﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Drawing;
using Petra.Gis.Geometry;
using Petra.Gis.Geometry.Support;
using Petra.Gis.Geometry.Utils;
using Petra.Gis.RenderingServices;
using Petra.Gis.RenderingServices.Common;


namespace Petra.Gis.RenderingServices.WPF
{

  public partial class WPFRenderer : Renderer
  {

    //important !!! this is array of output points for rendered geometries
    //should work as heap
    //GC should leave this variable untouched (same size) during procedure calls (and resize)
    //initial size is 100 000 pts, should be enough for typical geometry
    private System.Drawing.Point[] outPts = new System.Drawing.Point[100000];


    //draws polyline
    private void drawPolyline(LineString geom)
    {
      drawOpenGraphicsPath(geom.coordList.getBuffer(), geom.coordList.position, geom.coordList.Count, geom.coordList.dimension);
    }




    //draws composite curve
    private void drawPolyline(Petra.Gis.Geometry.Path geom)
    {
      foreach (GeometryBase g in geom)
      { 
        if(g.GetType() == typeof(LineString))
          drawOpenGraphicsPath(((LineString)g).coordList.getBuffer(),((LineString)g).coordList.position, ((LineString)g).coordList.Count, ((LineString)g).coordList.dimension);
        else if (g.GetType() == typeof(Arc))
          drawPolyline((Arc)g);

      }
    }




    //arc
    private void drawPolyline(Arc geom)
    {
      //if arc diameter is larger then precision of float, stroke (flatten) arc by Petra.Gis
      double d = unchecked((float)(geom.radius * 2));
      if(d != (geom.radius * 2))
      {
        drawPolyline(ArcMath.getStrokedArc(geom)); //currently stroke only, maybe implement arc clipping here
        return;
      }

      //stroke (flatten) by GDI+, transform by Petra.Gis
      PointF[] pts = gdiPlusStrokeArc(geom);
      drawOpenGraphicsPath(pts, 0, pts.Length);


      //or, transform arc and clip
      //todo: implement GDI+ clip !!
    }



    //multiline
    private void drawPolyline(MultiLine geom)
    {
      foreach (GeometryBase g in geom)
      {
        if (g.GetType() == typeof(LineString))
          drawOpenGraphicsPath(((LineString)g).coordList.getBuffer(), ((LineString)g).coordList.position, ((LineString)g).coordList.Count, ((LineString)g).coordList.dimension);
        else if (g.GetType() == typeof(Arc))
          drawPolyline((Arc)g);
        else if (g.GetType() == typeof(Petra.Gis.Geometry.Path))
          drawPolyline((Petra.Gis.Geometry.Path)g);

      }
    }






    /// <summary>
    /// process 3 operations in one step:
    /// change geometry spatial coordinates to screen coordinates
    /// remove duplicate verticies
    /// Sutherland-Hodgman polygon clip updated to single pass
    /// </summary>
    /// <param name="buffer">byte[] with geometry coordinates</param>
    /// <param name="start">start index in buffer</param>
    /// <param name="numPoints">number of points in array</param>
    /// <param name="dim">dimension of geometry (2d, 3d)</param>
    private void drawOpenGraphicsPath(byte[] buffer, int start, int numPoints, Dimension dim)
    {
      int numOfEdges = numPoints - 1;

      System.Drawing.Point startPt = new System.Drawing.Point();
      System.Drawing.Point endPt = new System.Drawing.Point();
      //System.Drawing.Point[] outPts = new System.Drawing.Point[numPoints];
      System.Drawing.Point[] retPts;
      PointD gisPt = new PointD();
      int lastPtPtr = -1;

      Array.Resize<System.Drawing.Point>(ref outPts, numPoints);

      MemoryStream m = new MemoryStream(buffer);
      m.Position = start;
      BinaryReader b = new BinaryReader(m);

      //get first point ordinates
      gisPt.X = b.ReadDouble();
      gisPt.Y = b.ReadDouble();
      if (dim == Dimension.dim3D)
        b.ReadDouble();

      //transform start point
      startPt = _transfMatrix.TransformPoint(gisPt);

      for (int i = 0; i < numOfEdges; i++)
      { 
        //get next point ordinates
        gisPt.X = b.ReadDouble();
        gisPt.Y = b.ReadDouble();
        if (dim == Dimension.dim3D)
          b.ReadDouble();

        //transform next point
        endPt = _transfMatrix.TransformPoint(gisPt);

        //   points not equal //and (both points are not in same zone or are inside bbox)
        if ((startPt != endPt) )//&& ((ims & ime) != ime || (ims | ime) == 0))
        {

          retPts = this.map.Extent.lineIntersect(startPt, endPt);

          if (retPts != null)
          {
            foreach (System.Drawing.Point p in retPts)
            {
              if (lastPtPtr == -1) //first point cant be drawn, just save it
              {
                outPts[0] = p;
                lastPtPtr = 0;
              }
              else
                if (p != outPts[lastPtPtr]) //eliminate duplicate points after clipping again
                {
                  outPts[lastPtPtr++] = p;
                }
            }
          }
        }
        startPt = endPt;
      }
      //redim outPts by last point
      if (lastPtPtr > 0)
      {
        Array.Resize<System.Drawing.Point>(ref outPts, lastPtPtr + 1);
        //add points to path
        _gdiPluspath.AddLines(outPts);
        //renderer path ??
      }
    }





    private void drawOpenGraphicsPath(PointF[] points, int start, int numPoints)
    {
      System.Drawing.Point startPt = new System.Drawing.Point();
      System.Drawing.Point endPt = new System.Drawing.Point();
      //System.Drawing.Point[] outPts = new System.Drawing.Point[numPoints - start];
      System.Drawing.Point[] retPts;
      int lastPtPtr = -1;

      int numOfEdges = numPoints - start - 1;

      Array.Resize<System.Drawing.Point>(ref outPts, numPoints);

      //transform start point
      startPt = _transfMatrix.transformPoint(points[start]);


      for (int i = start + 1; i < numOfEdges; i++)
      {

        endPt = _transfMatrix.transformPoint(points[i]);

        retPts = this.map.Extent.lineIntersect(startPt, endPt);

        if (retPts != null)
        {
          foreach (System.Drawing.Point p in retPts)
          {
            if (lastPtPtr == -1) //first point
            {
              outPts[0] = p;
              lastPtPtr = 0;
            }
            else
              if (p != outPts[lastPtPtr]) //eliminate duplicate points after clipping again
              {
                outPts[lastPtPtr++] = p;
              }
          }
        }
        startPt = endPt;
      }
      //redim outPts by last point
      if (lastPtPtr > 0)
      {
        Array.Resize<System.Drawing.Point>(ref outPts, lastPtPtr + 1);
        //add points to path
        _gdiPluspath.AddLines(outPts);
        //renderer path ??
      }
    }

  }
}
